package com.moyun.syssecurity.config;

import cn.hutool.core.convert.Convert;
import com.moyun.model.po.system.*;
import com.moyun.mapper.system.SysMenuMapper;
import com.moyun.mapper.system.SysPrivilegeMapper;
import com.moyun.mapper.system.SysRoleMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * @ClassName MySecurityMetadataSource
 * @Description 从DB动态加载规则
 * 配置认证数据源，实现动态权限加载（注意：不要手动new，把它交给spring管理，spring默认单例）
 * SecurityMetaDataSource接口负责提供受保护对象所需要的权限.由于受保护对象所需要的权限保存在数据库中,
 * 所以我们可以通过自定义类继承自FilterInvocationSecurityMetadataSource, 并重写getAttributes方法来提供受保护对象所需要的权限
 * @Author yangboxuan
 * @Date 2021/6/29 19:39
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class MySecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
    // 获取 AntPathMatcher 对象
    private final AntPathMatcher antPathMatcher = new AntPathMatcher();
    // 权限数据
    private static Map<String, List<String>> URIRolesMap = new ConcurrentHashMap<String, List<String>>();

    private final SysRoleMapper sysRoleMapper;
    private final SysMenuMapper sysMenuMapper;
    private final SysPrivilegeMapper sysPrivilegeMapper;

    List<SysRoleEntity> sysRoleEntities = null;


    /**
     * @return java.util.Collection<org.springframework.security.access.ConfigAttribute>
     * @Author yangboxuan
     * @Description Object 是受保护对象 FilterInvocation 返回值是访问受保护对象所需要的权限
     * 在我们初始化的权限数据中找到对应当前url的权限数据
     * @Date 2021/6/29 19:47
     * @Param [o]
     */
    @Override
    public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
        // 1.从受保护对象 FilterInvocation 中提取出当前请求的URL地址
        FilterInvocation fi = (FilterInvocation) o;
        String requestURI = fi.getRequestUrl();

        // 2. 获取访问该URI 所需要的角色集合
        // 方式一: 直接传入 uri 返回对应的角色结合  弊端: 太鸡肋,每个uri都需要走数据库进行判断
//        ArrayList<String> roleNames = getRoleNames(requestURI);

        // 方式二: 获取内存中保存的 URIRolesMap (uri-roles的映射map)
        List<String> roleNames = new ArrayList<>();
        for (Map.Entry<String, List<String>> entry : URIRolesMap.entrySet()) {
            if (requestURI.equals(entry.getKey())) {
                roleNames = entry.getValue();
            }
        }

        log.info("访问该URI:{},需要的角色权限为:{}", requestURI, roleNames);
        /**
         * 返回代码定义的默认配置 返回null时,允许访问受保护对象
         * 如果 当前请求的URL地址 和 数据库中记录的所有URL路径 都匹配不上,那么最终返回null
         * 如果返回null,那么受保护的对象能不能访问要看AbstractSecurityInterceptor对象中的rejectPublicInvocations属性,
         * 该属性默认为false,表示当getAttributes方法返回null时,允许访问受保护对象.
         */
        String[] strings = Convert.toStrArray(roleNames);
        if (roleNames.isEmpty()) {
            return null;
        } else {
            return SecurityConfig.createList(strings);
        }
    }

    private ArrayList<String> getRoleNames(String requestURI) {
        // 2.获取角色集合
        sysRoleEntities = this.sysRoleMapper.selectRolesWithMenus(null);
        ArrayList<String> roleNames = new ArrayList<>();
        // 3.遍历角色中的菜单集合 取出数据库中对应的URL集合
        for (SysRoleEntity sysRoleEntity : sysRoleEntities) {
            List<SysMenuEntity> menus = sysRoleEntity.getSysMenuEntityList();
            ArrayList<String> datebaseURIs = new ArrayList<>();
            menus.forEach((menu) -> datebaseURIs.add(menu.getMenuPath()));
            for (String datebaseURI : datebaseURIs) {
//                URIRolesMap.put(datebaseURI, roleNames);
                // 如果当前请求的URL地址和菜单中某一记录的path属性匹配上了(例如/admin/hello 匹配上/admin/**),那么我们就可以获取当前请求所需要的权限.
                if (antPathMatcher.match(datebaseURI, requestURI)) {
                    // 通过SecurityConfig.createList 方法创建一个Collention<ConfigAttribute>对象并返回
//                    return SecurityConfig.createList(sysRoleEntity.getRoleName());
                    roleNames.add(sysRoleEntity.getRoleName());
                }
            }
        }
        sysRoleEntities = this.sysRoleMapper.selectRolesWithPrivileges(null);
        // 4.遍历角色中权限集合 取出数据库中对应的URL集合
        for (SysRoleEntity sysRoleEntity : sysRoleEntities) {
            List<SysPrivilegeEntity> privileges = sysRoleEntity.getSysPrivilegeEntityList();
            ArrayList<String> datebaseURIs = new ArrayList<>();
            privileges.forEach((privilege) -> datebaseURIs.add(privilege.getPrivilegeUrl()));
            for (String datebaseURI : datebaseURIs) {
                // 如果当前请求的URL地址和菜单中某一记录的path属性匹配上了(例如/admin/hello 匹配上/admin/**),那么我们就可以获取当前请求所需要的权限.
                if (antPathMatcher.match(datebaseURI, requestURI)) {
                    // 通过SecurityConfig.createList 方法创建一个Collention<ConfigAttribute>对象并返回
//                    return SecurityConfig.createList(sysRoleEntity.getRoleName());
                    roleNames.add(sysRoleEntity.getRoleName());
                }
            }
        }
        return roleNames;
    }

    /**
     * @return java.util.Collection<org.springframework.security.access.ConfigAttribute>
     * @Author yangboxuan
     * @Description 可以用来返回所有的权限属性, 以便在项目启动阶段做校验, 如果不需要校验, 则直接返回null即可.
     * @Date 2021/6/29 20:00
     * @Param []
     */
    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    /**
     * @return boolean
     * @Author yangboxuan
     * @Description 表示当前对象支持处理的受保护对象是 FilterInvocation
     * @Date 2021/6/29 19:59
     * @Param [clazz]
     */
    @Override
    public boolean supports(Class<?> clazz) {
        return FilterInvocation.class.isAssignableFrom(clazz);
    }

    /**
     * 更新权限集合  (对应的 是数据库中 sys_role 这张表)
     */
//    public Map updateURIRoleMap() {
//        Map<String, List<String>> URIRolesMap = new ConcurrentHashMap<String, List<String>>();
//        // 1. 获取 每个菜单URI 对应的角色集合
//        List<SysMenuRoleEntity> sysMenuRoleEntities = this.sysMenuMapper.selectMenusWithRoles();
//        for (SysMenuRoleEntity sysMenuRoleEntity : sysMenuRoleEntities) {
//            URIRolesMap.put(sysMenuRoleEntity.getMenuPath(), sysMenuRoleEntity.getSysRoleEntityList().stream().map(r -> r.getRoleName()).collect(Collectors.toList()));
//        }
//        // 1. 获取 每个权限URI 对应的角色集合
//        List<SysPrivilegeRoleEntity> sysPrivilegeRoleEntities = this.sysPrivilegeMapper.selectPrivilegesWithRoles();
//        for (SysPrivilegeRoleEntity sysPrivilegeRoleEntity : sysPrivilegeRoleEntities) {
//            URIRolesMap.put(sysPrivilegeRoleEntity.getPrivilegeUrl(), sysPrivilegeRoleEntity.getSysRoleEntityList().stream().map(r -> r.getRoleName()).collect(Collectors.toList()));
//        }
//        MySecurityMetadataSource.URIRolesMap = URIRolesMap;
//        return URIRolesMap;
//
//    }

    /**
     * 更新权限集合  (对应的 是数据库中 sys_org_role 这张表)
     */
    public Map updateURIRoleMap() {
        Map<String, List<String>> URIRolesMap = new ConcurrentHashMap<String, List<String>>();
        // 1. 获取 每个菜单URI 对应的角色集合
        List<SysMenuRoleEntity> sysMenuRoleEntities = this.sysMenuMapper.selectMenusWithOrgRoles();
        for (SysMenuRoleEntity sysMenuRoleEntity : sysMenuRoleEntities) {
            URIRolesMap.put(sysMenuRoleEntity.getMenuPath(), sysMenuRoleEntity.getSysOrgRoleEntityList().stream().map(r -> r.getNumber()).collect(Collectors.toList()));
        }
        // 1. 获取 每个权限URI 对应的角色集合
        List<SysPrivilegeRoleEntity> sysPrivilegeRoleEntities = this.sysPrivilegeMapper.selectPrivilegesWithOrgRoles();
        for (SysPrivilegeRoleEntity sysPrivilegeRoleEntity : sysPrivilegeRoleEntities) {
            URIRolesMap.put(sysPrivilegeRoleEntity.getPrivilegeUrl(), sysPrivilegeRoleEntity.getSysOrgRoleEntityList().stream().map(r -> r.getNumber()).collect(Collectors.toList()));
        }
        MySecurityMetadataSource.URIRolesMap = URIRolesMap;
        return URIRolesMap;

    }
}
